import xml.etree.ElementTree as ET
from xml.etree.ElementTree import Element
from xml.dom import minidom
from typing import List, Tuple
import argparse
import os, sys
import re
import subprocess
import time
import json
import importlib
import socket
import datetime
import shutil
import importlib.util
import filecmp
import math

# 进程需要停止很久的funcid
LONG_TIME_STOP_FUNCID = [55, 58, 53, 103, 105, 109]
# 老的规则，已经废弃：服务端口规则
PORT_RULE = {
    # 对应lsio的端口，也就是lssvr的前端tconnd
    "lssvr": [
        {
            "id1": 6629,  # 第一个实例端口
            "other": 8000  # 非第一个实例端口的基数，端口号为：基数+instid
        },
    ],
    "lockstepsvr": [
        # 对应lockstepio1的端口
        {
            "id1": 6630,
            "other": 12000
        },
        # 对应lockstepio2的端口
        {
            "id1": 6631,
            "other": 13000
        },
    ]
}

# 协议类型
PROTOCAL_RULE = {
    "tcp": ["gamesvr"],
    "udp": ["lockstepsvr"],
}

# TCONND 端口funcid
TCONND_FUNC_RULE = {
    "lockstepsvr": [6, 7],
    "gamesvr": [1],
}

# proc_deploy instance组大id，需要通过tcmcenter.xml配置AddrTemple
MAX_INSTID = math.pow(2, 15)

SET1_MAXINSTID = 7999
SET2_MAXINSTID = 15999


# 一机多实例部署配置
MULT_INSTANCE_RULE = {
    "gamecoresvr": 14,
}

# 新的端口规则
RS_PORT_RULE = {
    "lssvr": [10500],  # lssvr的base port
    "lockstepsvr": [10600, 10700],  # lsio1 lsio2的base port
    "step": 99,  # index最大值
}

# VPORT端口信息
VPORT_RULE = {
    "ce": {
        "lssvr": [20000],
        "lockstepsvr": [30000, 36000],
        "step": 3999,  # 最多vport数量
    },
    "test": {
        "lssvr": [61000],
        "lockstepsvr": [61100, 61200],
        "step": 100,  # 最多vport数量
    },
    "press": {
        "lssvr": [61300],
        "lockstepsvr": [61400, 61500],
        "step": 100,  # 最多vport数量
    },
    "pre": {
        "lssvr": [60000],
        "lockstepsvr": [60100, 60200],
        "step": 100,  # 最多vport数量
    },
}

# 黑名单端口映射，将黑名单端口替换为另外的端口
BLACKPORT_REPLACE = {
    1: 2,
}
PORT_CFG = {}


class XmlParser:
    def __init__(self, filepath, fileType) -> None:
        self.filepath = filepath
        self.mytree = ET.parse(self.filepath)
        self.myroot = self.mytree.getroot()
        self.filetype = fileType
        self.check_file()

    def check_file(self):
        success = False
        CheckTag = ""
        if self.filetype == "host":
            CheckTag = "HostTab"
        elif self.filetype == "proc":
            CheckTag = "cluster"
        elif self.filetype == "proc_deploy":
            CheckTag = "ClusterDeploy"
        elif self.filetype == "bus_relation":
            CheckTag = "TbusConf"
        else:
            raise Exception("unsuport type: %s" % (self.filetype))

        for i in self.myroot:
            if i.tag == CheckTag:
                success = True
                break

        if not success:
            raise Exception("not found type: %s tag: %s" % (self.filetype, CheckTag))

    def write(self):
        # if hasattr(ET, "indent"):
        #     ET.indent(self.mytree, "    ")
        #     self.mytree.write(self.filepath)
        # else:
        s = minidom.parseString(ET.tostring(self.myroot)).toprettyxml(indent="   ")
        lines = [i for i in s.split("\n") if i.strip() != ""]
        with open(self.filepath, "w") as f:
            f.write("\n".join(lines))


class BusRelationParser(XmlParser):
    def __init__(self, filepath, world_id=1) -> None:
        super().__init__(filepath, "bus_relation")
        self.tbus_conf = self.get_tbusconf_node()

    def check_need_refresh_tbus(self, func_name_list: List[str]):
        for i in self.tbus_conf:
            if i.tag == "Channel":
                if i.attrib.get("ProcSet1") in func_name_list or i.attrib.get("ProcSet2") in func_name_list:
                    return True
        return False

    def get_tbusconf_node(self):
        for i in self.myroot:
            if i.tag == "TbusConf":
                return i
        raise Exception("get TbusConf failed")


class ProcDeployParser(XmlParser):
    def __init__(self, filepath, world_id=1) -> None:
        super().__init__(filepath, "proc_deploy")
        self.world_id = world_id
        self.world = self.get_world_node(world_id)
        self.zone = self.get_zone_node(world_id)
        self.cluster = self.get_cluster_node()

    def add_proc_deploy(self, ProcGroupName, HostName, InstId, index=None, vport=None, rsport=None):

        if ProcGroupName in zone_group_name:
            ele = ET.SubElement(self.zone[args.zoneid-1], "DeloyGroup", {"Group": ProcGroupName, "Host": HostName, "InstID": str(InstId)})
        else:
            ele = ET.SubElement(self.world, "DeloyGroup", {"Group": ProcGroupName, "Host": HostName, "InstID": str(InstId)})

        if index != None and vport != None and rsport != None:
            ele.attrib["CustomAttr"] = "INDEX=%d;VPORT=%s;RSPORT=%s" % (index, vport, rsport)
        return ele

    def add_runshell_deploygroup(self, HostName, InstId):
        # ET.SubElement(self.cluster, "DeloyGroup", {"Group": "run_shell", "Host": HostName, "InstID": str(InstId)})
        new_elm = ET.Element("DeloyGroup", {"Group": "run_shell", "Host": HostName, "InstID": str(InstId)})
        self.cluster.insert(len(self.cluster) - 1, new_elm)

    def get_hostname_deploygroup(self, hostname) -> List[Element]:
        DeloyGroupNodes = []
        for k in self.world:
            if k.tag == "DeloyGroup" and k.attrib.get("Host") == hostname:
                DeloyGroupNodes.append(k)
        for k in self.zone:
            for j in k:
                if j.tag == "DeloyGroup" and j.attrib.get("Host") == hostname:
                    DeloyGroupNodes.append(j)
        return DeloyGroupNodes

    def get_hostname_funcid_instid(self, hostname, proc_filepath):
        deploygroups = self.get_hostname_deploygroup(hostname)
        proc_xml = ProcParser(proc_filepath)
        result = []
        for i in deploygroups:
            groupname = i.attrib.get("Group")
            func_names = proc_xml.get_proc_group_func_names(groupname)
            func_ids = proc_xml.get_funcname_instid(*func_names).values()
            instid = i.attrib.get("InstID")
            print("groupname: %s, func_names: %s, funcids: %s, instid: %s" % (groupname, func_names, func_ids, instid))
            result.append({"groupname": groupname, "func_names": func_names, "func_ids": func_ids, "inst_id": instid})
        return result

    def get_funcid_and_instid_list(self, hostname, group_name, proc_filepath):
        print("get func id and instid", hostname, group_name)
        proc_xml = ProcParser(proc_filepath)
        result = []
        for i in self.world:
            if i.tag == "DeloyGroup" and i.attrib.get("Group") == group_name and i.attrib.get("Host") == hostname:
                instid = i.attrib.get("InstID")
                func_names = proc_xml.get_proc_group_func_names(group_name)
                func_names_to_ids = proc_xml.get_funcname_instid(*func_names)
                print("groupname: %s, func_names: %s, funcids: %s, instid: %s" % (
                group_name, str(func_names), str(func_names_to_ids), instid))
                result.append([func_names_to_ids, instid])
                # return func_names_to_ids, instid
        if len(result) == 0:
            raise Exception("not get this deploy info, hostname: %s, group_name: %s" % (hostname, group_name))
        return result

    def get_hostname_runshell_instid(self, hostname: str):
        for i in self.cluster:
            if i.tag == "DeloyGroup" and i.attrib.get("Group") == "run_shell" and i.attrib.get("Host") == hostname:
                return i.attrib.get("InstID")
        raise Exception("get hostname %s runshell id failed" % (hostname))

    def get_hostnames_runshell_instids(self, hostnames: List[str]) -> List[str]:
        result = []
        for hostname in hostnames:
            result.append(self.get_hostname_runshell_instid(hostname))
        return result

    def del_proc_deploy_by_name(self, ProcGroupName, InstId):
        # 一定要倒叙，否则会导致部分删不到
        for index in range(len(self.world) - 1, -1, -1):
            i = self.world[index]
            if i.tag == "DeloyGroup" and i.attrib.get("Group") == ProcGroupName and i.attrib.get("InstID") == InstId:
                print("remove item tag: %s, attrs: %s" % (i.tag, i.attrib))
                self.world.remove(i)

    def del_proc_deploy_by_hostname(self, hostname):
        # 一定要倒叙，否则会导致部分删不到
        for index in range(len(self.world) - 1, -1, -1):
            i = self.world[index]
            if i.tag == "DeloyGroup" and i.attrib.get("Host") == hostname:
                print("remove item tag: %s, attrs: %s" % (i.tag, i.attrib))
                self.world.remove(i)
        reversed_zones = self.zone[::-1]
        for in_zone in reversed_zones:
            for index in range(len(in_zone) - 1, -1, -1):
                i = in_zone[index]
                if i.tag == "DeloyGroup" and i.attrib.get("Host") == hostname:
                    print("remove item tag: %s, attrs: %s" % (i.tag, i.attrib))
                    in_zone.remove(i)



    def del_runshell_by_hostname(self, hostname):
        # 一定要倒叙，否则会导致部分删不到
        for index in range(len(self.cluster) - 1, -1, -1):
            i = self.cluster[index]
            if i.tag == "DeloyGroup" and i.attrib.get("Group") == "run_shell" and i.attrib.get("Host") == hostname:
                print("remove item tag: %s, attrs: %s" % (i.tag, i.attrib))
                self.cluster.remove(i)

    def get_world_node(self, InstId):
        TagName = "world"
        for zone in self.get_cluster_node():
            if zone.tag == TagName and int(zone.attrib.get("ID")) == InstId:
                return zone
        raise Exception("get node failed, tag: %s, inst: %s" % (TagName, InstId))

    def get_zone_node(self, InstId):
        zone_nodes = []
        TagName = "zone"
        for i in self.get_world_node(InstId):
            if i.tag == TagName:
                zone_nodes.append(i)
        return zone_nodes

    def get_cluster_node(self):
        for i in self.myroot:
            if i.tag == "ClusterDeploy":
                return i
        raise Exception("get ClusterDeploy failed")

    def get_runshell_max_instid(self):
        maxid = 0
        for i in self.cluster:
            if i.tag != "DeloyGroup" and i.attrib.get("Group") != "run_shell":
                continue
            if int(i.attrib.get("InstID")) > maxid:
                maxid = int(i.attrib.get("InstID"))
        return maxid

    def _check_instid_available(self, instid):
        if instid >= MAX_INSTID:
            raise Exception("instid more than %d, is %d" % (MAX_INSTID, instid))

    def get_runshell_avaiable_instid(self):
        runshell_instids = []
        for i in self.cluster:
            if i.tag != "DeloyGroup" and i.attrib.get("Group") != "run_shell":
                continue
            runshell_instids.append(int(i.attrib.get("InstID")))

        start = 1
        while True:
            if start not in runshell_instids:
                self._check_instid_available(start)
                return start
            start += 1

    def get_avaiable_instid(self, ProcGroupName):
        '''
        @note 获取可用的instid
        '''
        current_instids = []
        zone_svr = ["pvpsvr", "gamesvr", "lockstepmgrsvr", "lockstepsvr"]
        for i in self.world:
            if i.attrib.get("Group") == ProcGroupName:
                current_instids.append(int(i.attrib.get("InstID")))
        for i in self.zone:
            for j in i:
                if j.attrib.get("Group") == ProcGroupName:
                    current_instids.append(int(j.attrib.get("InstID")))
        print(current_instids)
        current_instids.sort()
        start = 1
        if ProcGroupName in zone_svr:
            # zone_id为2
            if current_instids[-1] >= 8000:
                start = 8000
                max_instid = SET2_MAXINSTID
            else:
                max_instid = SET1_MAXINSTID
            while True:
                if start not in current_instids:
                    if start > max_instid:
                        raise Exception("instid more than %d, is %d" % (max_instid, start))
                    return start
                start += 1
        else:
            while True:
                if start not in current_instids:
                    self._check_instid_available(start)
                    return start
                start += 1

    def get_group_max_instid(self, ProcGroupName):
        maxid = 0
        for i in self.world:
            if i.attrib.get("Group") == ProcGroupName:
                if int(i.attrib.get("InstID")) > maxid:
                    maxid = int(i.attrib.get("InstID"))
        return maxid


class HostParser(XmlParser):
    def __init__(self, filepath) -> None:
        super().__init__(filepath, "host")
        self.parent_node = self.get_host_node()

    def add_host(self, hostname, innerip):
        sub = ET.SubElement(self.parent_node, "Host", {"Name": hostname, "InnerIP": innerip})
        sub.text = "\n        "

    def del_host(self, innerip):
        for i in self.parent_node:
            if i.attrib.get("InnerIP") == innerip:
                print("remove host tag: %s, attrs: %s" % (i.tag, i.attrib))
                self.parent_node.remove(i)

    def get_hostname(self, innerip):
        for i in self.parent_node:
            if i.attrib.get("InnerIP") == innerip:
                return i.attrib.get("Name")
        raise Exception("found innerip %s failed" % (innerip))

    def get_hostnames(self, innerips: List[str]) -> List[str]:
        hostnames = []
        for ip in innerips:
            hostnames.append(self.get_hostname(ip))
        return hostnames

    def get_host_node(self):
        for zone in self.myroot:
            if zone.tag == "HostTab":
                return zone
        raise Exception("get host node failed")

    def get_new_hostname(self, env, svc):
        for index in range(1, 100000):
            new_hostname = "osgame_%s_%s%d" % (env, svc, index)
            exists = False
            for i in self.parent_node:
                if i.attrib.get("Name") == new_hostname:
                    exists = True
                    break
            if not exists:
                return new_hostname
        else:
            raise Exception("can not create new hostname for svc %s" % (svc))


class ProcParser(XmlParser):
    def __init__(self, filepath) -> None:
        super().__init__(filepath, "proc")
        self.cluster_node = self.get_cluster_node()
        self.world_node = self.get_world_node()

    def get_cluster_node(self):
        return self.myroot.find("cluster")

    def get_world_node(self):
        return self.myroot.find("cluster/world")

    def get_proc_group(self, name):
        for i in self.myroot:
            if i.tag == "ProcGroup" and i.attrib.get("Name") == name:
                return i
        raise Exception("can not found proc_group: %s" % (name))

    def get_proc_funcname(self, funcid):
        for i in self.world_node:
            funcname = i.attrib.get("FuncName")
            _funcid = i.attrib.get("FuncID")
            if i.tag == "Proc" and funcid == _funcid:
                print("instid %s get funcname %s" % (funcid, funcname))
                return funcname

        raise Exception("get func id %s funcname failed" % (funcid))

    def get_proc_name_belong_group_name(self, proc_name):
        for i in self.myroot:
            if i.tag == "ProcGroup":
                for j in i:
                    if j.tag == "Proc" and j.attrib.get("FuncName") == proc_name:
                        return i.attrib.get("Name")
        raise Exception("can not found proc_name %s belong to which group_name" % (proc_name))

    def get_proc_group_func_names(self, name):
        proc_group = self.get_proc_group(name)
        func_names = []
        for node in proc_group:
            if node.tag == "Proc":
                func_names.append(node.attrib.get("FuncName"))
        return func_names

    def get_funcname_instid(self, *names):
        # 返回funcname对应的id列表，一定按顺序返回回去
        if len(names) == 0:
            return {}

        m = {}
        for i in self.world_node:
            funcname = i.attrib.get("FuncName")
            instid = i.attrib.get("FuncID")
            if i.tag == "Proc" and funcname in names:
                print("funcname %s get instid %s" % (funcname, instid))
                m[funcname] = int(instid)

        if len(names) != len(m):
            raise Exception("names num %d not match instids num %d, names: %s" % (len(names), len(m), str(names)))
        return m


def tcm_cmd(cmdstr, timeout=180):
    '''
    @note 执行tcm指令
    '''
    print("execute cmd: ", cmdstr)
    if DEBUG:
        return ""
    cmd = subprocess.run(["./tcmconsole", "--timeout=%d" % (timeout), "--id=scale", "--conf-file=../cfg/tcmconsole.xml",
                          "--tlogconf=../cfg/tcmconsole_log.xml", "--cmd", cmdstr], stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
    if cmd.returncode != 0:
        raise Exception("execute tcm cmd : %s, failed, status: %d, stdout: %s, stderr: %s" % (
        cmdstr, cmd.returncode, cmd.stdout.decode("utf8"), cmd.stderr.decode("utf8")))
    print("execute tcm cmd: %s success, stdout: %s" % (cmdstr, cmd.stdout.decode("utf8")))
    sys.stdout.flush()
    return cmd.stdout.decode("utf8")


def run_cmd(cmd_list):
    '''
    @note 执行Linux命令
    '''
    print("execute cmd: ", cmd_list)
    if DEBUG:
        return ""
    cmd = subprocess.run(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if cmd.returncode != 0:
        raise Exception("execute tcm cmd : %s, failed, status: %d, stdout: %s, stderr: %s" % (
        cmd_list, cmd.returncode, cmd.stdout.decode("utf8"), cmd.stderr.decode("utf8")))
    print("execute tcm cmd: %s success, stdout: %s" % (cmd_list, cmd.stdout.decode("utf8")))
    sys.stdout.flush()
    return cmd.stdout.decode("utf8")


# 有的进程要停很久
def long_time_stop(proc_id: str):
    '''
    @note 需要停很久的进程stop
    '''
    print("execute cmd: stop %s" % (proc_id))
    if DEBUG:
        return
    try:
        out = tcm_cmd("stop %s" % (proc_id), 60)
    except Exception as e:
        if str(e).find("[TIMEOUT]") >= 0:
            print("execute stop timeout, will loop check, err: %s" % (str(e)))
        else:
            raise Exception("execute stop failed: %s" % (str(e)))

    for i in range(1200):
        time.sleep(5)
        try:
            out = tcm_cmd("check %s" % (proc_id))
            check_cmd_success(out, "check")
        except Exception as e2:
            print("check proc not running error: %s" % (str(e2)))
            continue

        for line in out.split("\n"):
            if line.find("NOT RUNNING") >= 0 and line.find("Proc:") >= 0:
                print("process %s had not running" % (proc_id))
                return


def check_cmd_success(out, cmd):
    '''
    @note 检查tcm指令是否执行成功
    '''
    if DEBUG:
        return True
    if out == None:
        raise Exception("out is none, check failed")
    if type(out) == bytes:
        out = out.decode("utf8")
    for i in out.split("\n"):
        if i.find("CMD>%s" % (cmd)) >= 0:
            if i.find("[SUCCEED]") < 0:
                raise Exception("check runshell result failed")
            else:
                return True
    raise Exception("check failed")


def uncompress_pkg(ips: List[str], args):
    '''
    @note 解压服务端程序
    '''
    hostnames = host_parser.get_hostnames(ips)
    runshell_ids = proc_deploy_parser.get_hostnames_runshell_instids(hostnames)
    out = tcm_cmd("runshell *.*.251.[%s] %s" % (",".join(runshell_ids), args), 600)
    check_cmd_success(out, "runshell")


def parse_custom_attr(content):
    '''
    @note 解析CustomAttr数据
    '''
    result = {}
    if content.strip() == "":
        return result

    kvs = content.split(";")
    for i in kvs:
        kv_list = i.split("=")
        if kv_list[1].find("[") >= 0:
            result[kv_list[0].strip()] = json.loads(kv_list[1])
        else:
            result[kv_list[0].strip()] = int(kv_list[1])

    return result


def get_clb_info(ips: List[str]):
    '''
    @note 获取IP的vport、rsport信息
    '''
    port_rules = []
    vports = {"gamesvr": [], "lockstepsvr": []}
    for ip in ips:
        hostname = host_parser.get_hostname(ip)
        deploy_groups = proc_deploy_parser.get_hostname_deploygroup(hostname)
        for i in deploy_groups:
            instid = i.attrib.get("InstID")
            group_name = i.attrib.get("Group").strip()
            CustomAttr = i.attrib.get("CustomAttr")

            if not CustomAttr:  # 为空说明没有配置vport
                continue
            attr = parse_custom_attr(CustomAttr)
            if type(attr["VPORT"]) == int:
                attr["VPORT"] = [attr["VPORT"]]
                attr["RSPORT"] = [attr["RSPORT"]]
            for index in range(len(attr["VPORT"])):
                port_rules.append("%s-%s-%s-%s" % (group_name, ip, attr["VPORT"][index], attr["RSPORT"][index]))

            vports.setdefault(group_name, [])
            vports[group_name] += attr["VPORT"]

    # 生成端口映射到配置
    print("<SOPS_VAR>ports:%s</SOPS_VAR>" % (",".join(port_rules)))
    for k, v in vports.items():
        print("<SOPS_VAR>%s_vports:%s</SOPS_VAR>" % (k, ",".join([str(i) for i in v])))


def del_config(ip_lists: List[str]):
    '''
    @note 缩容删除配置信息
    '''
    for ip in ip_list:
        hostname = host_parser.get_hostname(ip)
        proc_deploy_parser.del_runshell_by_hostname(hostname)
        proc_deploy_parser.del_proc_deploy_by_hostname(hostname)
        host_parser.del_host(ip)

    proc_deploy_parser.write()
    host_parser.write()


def generate_vport_rsport(func_names: List[str], instid: int, env: str, custom_index: int):
    '''
    @note 生成rsport vport信息
    '''
    vports = []
    rsports = []
    for func_name in func_names:
        vports = generate_vport(func_name, instid, env)
        if len(vports) > 0:
            rsports = generate_rsport(func_name, custom_index)
            break

    vports_str = None
    rsports_str = None
    if len(vports) == 1:
        vports_str = str(vports[0])
        rsports_str = str(rsports[0])
    elif len(vports) == 2:
        vports_str = json.dumps(vports).replace(" ", "")
        rsports_str = json.dumps(rsports).replace(" ", "")

    return vports_str, rsports_str


def add_config(env: str, ip_lists: List[str], svcs: List[str]):
    '''
    @note 扩容添加tcm配置
    '''
    # 检查扩容的IP没有在host.xml中
    for ip in ip_lists:
        if not re.match("^(\\d+\\.){3}\\d+$", ip):
            raise Exception("ip %s is not a right ip format" % (ip))

        # 检测IP是否已经存在
        check_ip_exists = True
        try:
            host_parser.get_hostname(ip)
        except Exception as e:
            if str(e).find("found innerip") >= 0:
                check_ip_exists = False
        if check_ip_exists:
            raise Exception("ip %s had exists in host.xml" % (ip))

    # 检查扩容的服务名称没有错误
    for svc in svcs:
        proc_parser.get_proc_group(svc)

    for ip in ip_lists:
        # 添加host
        new_hostname = host_parser.get_new_hostname(env, svcs[0])
        print("add host, hostname: %s, ip: %s" % (new_hostname, ip))
        host_parser.add_host(new_hostname, ip)
        print("add host success")

        # 添加服务
        for svc in svcs:
            instance_count = MULT_INSTANCE_RULE.get(svc, 1)
            func_names = proc_parser.get_proc_group_func_names(svc)
            for custom_index in range(1, instance_count + 1):
                instid = proc_deploy_parser.get_avaiable_instid(svc)

                # 获取vport rsport 信息
                vports_str, rsports_str = generate_vport_rsport(func_names, instid, env, custom_index)
                print("add proc_deploy: proc_group: %s hostname: %s inst_id: %d, index: %d, vports: %s, rsports: %s" % (
                svc, new_hostname, instid, custom_index, vports_str, rsports_str))

                proc_deploy_parser.add_proc_deploy(svc, new_hostname, instid, custom_index, vports_str, rsports_str)

        # 添加主机的runshell
        runshell_instid = proc_deploy_parser.get_runshell_avaiable_instid()
        proc_deploy_parser.add_runshell_deploygroup(new_hostname, runshell_instid)
        print("add runshell hostname %s instid %d success" % (new_hostname, runshell_instid))
    host_parser.write()
    proc_deploy_parser.write()


def pushcfg(ips: List[str]):
    # 推送配置
    print("start create and push cfg")
    for ip in ips:
        hostname = host_parser.get_hostname(ip)
        deploy_groups = proc_deploy_parser.get_hostname_deploygroup(hostname)
        all_func_names = []
        for i in deploy_groups:
            instid = i.attrib.get("InstID")
            group_name = i.attrib.get("Group")
            func_names = proc_parser.get_proc_group_func_names(group_name)
            all_func_names += func_names

            funcname_ids = proc_parser.get_funcname_instid(*func_names)
            ids = funcname_ids.values()
            result = tcm_cmd("RunShell *.*.250.* *.*.[%s].%s" % (",".join([str(i) for i in ids]), instid))
            check_cmd_success(result, "RunShell")

        # 生成配置
        # result = tcm_cmd("CreateCfghost *.*.*.* %s"%(ip))
        result = tcm_cmd("PushCfgHost *.*.*.* %s" % (ip))
        check_cmd_success(result, "PushCfgHost")

        # 刷新tbus通道
        if bus_relation_parser.check_need_refresh_tbus(all_func_names):
            out = tcm_cmd("refreshbuscfg *.*.*.* %s" % (hostname))
            check_cmd_success(out, "refreshbuscfg")


def get_tconnd_procid(hostname):
    '''
    @note 获取主机的tconnd进程id
    '''
    proc_deploys = proc_deploy_parser.get_hostname_deploygroup(hostname)
    ids = []
    for proc_deploy in proc_deploys:
        Group = proc_deploy.attrib.get("Group")
        instid = proc_deploy.attrib.get("InstID")
        if Group in TCONND_FUNC_RULE:
            funcids = TCONND_FUNC_RULE[Group]
            funcids = [str(i) for i in funcids]
            ids.append("*.*.[%s].%s" % (",".join(funcids), instid))
    return ids


def start_tconnd(ips: List[str]):
    '''
    @note  important!@!!!!!!!!!!必须先推配置, 再刷tbus, 再启动, 不能启动一半然后刷tbus!!!
    '''
    hostnames = host_parser.get_hostnames(ips)
    for hostname in hostnames:
        tconnd_procids = get_tconnd_procid(hostname)
        if len(tconnd_procids) == 0:
            continue
        for procid in tconnd_procids:
            result = tcm_cmd("start %s" % (procid))
            check_cmd_success(result, "start")


def start(ips: List[str], DisableTconnd=False):
    '''
    @note  important!@!!!!!!!!!!必须先推配置, 再刷tbus, 再启动, 不能启动一半然后刷tbus!!!
    '''
    for ip in ips:
        if ip.strip() == "":
            continue
        hostname = host_parser.get_hostname(ip)

        tconnd_procids = get_tconnd_procid(hostname)

        # 生成禁用tconnd的语句
        disabe_tconnd_cmdstr = ""
        if DisableTconnd and len(tconnd_procids) > 0:
            for tconnd_procid in tconnd_procids:
                disabe_tconnd_cmdstr = "%sdisable %s;" % (disabe_tconnd_cmdstr, tconnd_procid)
                # 检查tconnd已经启动，否则直接报错
                check_hostname_proc_status(proc_id=tconnd_procid, running=True)

        result = tcm_cmd("%sStartHost *.*.*.* %s" % (disabe_tconnd_cmdstr, ip))
        check_cmd_success(result, "StartHost")

        check_hostname_proc_status(hostname, running=True)


def get_ordered_stop_funcid_list(group_name):
    '''
    @note 生成排序后的停服funcid列表
    '''
    func_names = proc_parser.get_proc_group_func_names(group_name)
    func_names_to_ids = proc_parser.get_funcname_instid(*func_names)

    order_id = []
    if group_name == "lockstepsvr":
        order_id = [53, 115, 6, 7, 153]
    elif group_name == "gamesvr":
        order_id = [55, 1, 109, 155]

    func_ids = []
    # 生成停服进程顺序
    if len(order_id) > 0:
        tmp_order_ids = func_names_to_ids.values()
        for id in order_id:
            if id in tmp_order_ids:
                func_ids.append(id)
        for id in tmp_order_ids:
            if id not in order_id:
                func_ids.append(id)
    else:
        # 获取不包含sidecar的进程id
        for k, v in func_names_to_ids.items():
            if not k.endswith("_sidecar"):
                func_ids.append(v)
        for k, v in func_names_to_ids.items():
            if k.endswith("_sidecar"):
                func_ids.append(v)

    return func_ids


def stop(ips: List[str]):
    '''
    @note 缩容停服
    '''
    for ip in ips:
        hostname = host_parser.get_hostname(ip)
        deploy_groups = proc_deploy_parser.get_hostname_deploygroup(hostname)
        for group in deploy_groups:
            group_name = group.attrib.get("Group")
            instid = group.attrib.get("InstID")
            print("start stop ip: %s, hostname: %s, group_name: %s, instid: %s" % (ip, hostname, group_name, instid))
            ordered_func_ids = get_ordered_stop_funcid_list(group_name)

            # 按序进行停服
            for func_id in ordered_func_ids:
                current_proc_id = "*.*.%d.%s" % (func_id, str(instid))
                if func_id in LONG_TIME_STOP_FUNCID:
                    long_time_stop(current_proc_id)
                else:
                    out = tcm_cmd("stop %s" % (current_proc_id))
                    check_cmd_success(out, "stop")
        print("ip %s had stop all proccess" % (ip))
        check_hostname_proc_status(ip, running=False)


def check_hostname_proc_status(hostname: str = "", proc_id: str = "", running=True):
    '''
    @note 检查主机的进程状态
    '''
    proc_info = proc_id
    if proc_id == "":
        proc_info = "*.*.*.*"
    if hostname != "":
        proc_info = "%s %s" % (proc_info, hostname)

    if running:
        cmd = "checkno %s" % (proc_info)
        out = tcm_cmd(cmd)
        check_cmd_success(out, "checkno")
        if not DEBUG and out.find("[NOT RUNNING]") >= 0:
            raise Exception("had proc not running: %s" % (out))
        else:
            return
    else:
        cmd = "checkbe %s" % (proc_info)
        out = tcm_cmd(cmd)
        check_cmd_success(out, "checkbe")
        if not DEBUG and out.find("[RUNNING]") >= 0:
            raise Exception("had proc running: %s" % (out))
        else:
            return


def generate_rsport(func_name: str, custom_index: int) -> List[int]:
    '''
    @note 生成rsport端口
    '''
    result_port = []

    step = RS_PORT_RULE["step"]
    if custom_index >= step:
        raise Exception("custom_index cannot more than %d, this is %d" % (step, custom_index))

    if func_name in RS_PORT_RULE.keys():
        svc_port_info = RS_PORT_RULE[func_name]
        for i in svc_port_info:
            result_port.append(i + custom_index)
    return result_port


def generate_vport(func_name: str, instid: int, env: str) -> List[int]:
    '''
    @note 生成vport端口
    '''
    port_info = VPORT_RULE[env]
    step = port_info["step"]
    if instid < 8000:
        if instid >= step:
            raise Exception("inst id %d cannot out step %d" % (instid, step))

    vports = []

    if func_name in port_info:
        ports = port_info[func_name]
        for i in ports:
            vport = i + instid
            vport = BLACKPORT_REPLACE.get(vport, vport)  # 替换黑名单端口
            vports.append(vport)

    return vports


def get_local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(("8.8.8.8", 80))
    return s.getsockname()[0]


def lssvr_tickoff(ip_list: List[str]):
    # 获取IP列表里面gamesvr的inst id
    gamesvr_instids = []
    for ip in ip_list:
        hostname = host_parser.get_hostname(ip)
        host_deploy_groups = proc_deploy_parser.get_hostname_deploygroup(hostname)
        for i in host_deploy_groups:
            if i.attrib.get("Group") != "gamesvr":
                continue
            gamesvr_instids.append(i.attrib.get("InstID"))

    print("ips %s get gamesvr instids %s" % (str(ip_list), str(gamesvr_instids)))

    # 对lssvr进行踢人
    result = tcm_cmd("runshell *.*.55.[%s] disablelsload" % (",".join(gamesvr_instids)))
    check_cmd_success(result, "runshell")
    print("<SOPS_VAR>lssvrs:%s</SOPS_VAR>" % (",".join(gamesvr_instids)))


def get_deploy_names(ips: List[str]):
    deploy_names = set()
    compare_ip = ""
    for ip in ips:
        hostname = host_parser.get_hostname(ip)
        deploy_eles = proc_deploy_parser.get_hostname_deploygroup(hostname)
        c_names = set()
        for deploy in deploy_eles:
            c_names.add(deploy.attrib.get("Group"))
        if not deploy_names:
            deploy_names = c_names
            compare_ip = ip

        diff = deploy_names.difference(c_names)
        if len(diff) > 0:
            raise Exception("ip %s had difference deploy_group to %s: %s" % (ip, compare_ip, diff))

    print("<SOPS_VAR>deploy_names:%s</SOPS_VAR>" % (",".join(deploy_names)))


def parse():
    parser = argparse.ArgumentParser(prog="xml_parser", description="tcm配置查看修改")
    parser.add_argument("-t", "--type",
                        choices=["addcfg", "pushcfg", "start_tconnd", "start", "stop", "uncompress", "tickoff",
                                 "clbinfo", "delcfg", "getnames"], required=True, help="操作类型")
    parser.add_argument("-i", "--ips", required=True, help="操作IP列表, 多个以逗号隔开, 比如: 1.1.1.1,1.1.1.2")
    parser.add_argument("-n", "--group_name", help="全局模块下扩容的deploy_group名称(zone模块下不可填写), 多个以逗号隔开, 比如: consul,infosvr")
    parser.add_argument("-N", "--zgroup_name", help="zone模块下扩容的deploy_group名称(pvpsvr,gamesvr,lockstepsvr,lockstepmgrsvr), 只能填写一个")
    parser.add_argument("-z", "--zoneid", type=int, default=1, choices=[1, 2], help="proc_deploy.xml中zone的id【1-2】")
    parser.add_argument("-e", "--env", help="环境名称", choices=["ce", "pre", "test", "press"])
    parser.add_argument("-w", "--worldid", type=int, default=1, help="proc_deploy.xml中world的id")
    parser.add_argument("-a", "--args", help="解压需要的其它参数")
    parser.add_argument("-d", "--debug", action='store_true', help="调试模式, 不会执行具体的指令")
    return parser.parse_args()


if __name__ == "__main__":
    print(sys.argv)

    args = parse()
    DEBUG = args.debug

    if not DEBUG:
        workdir = "/data/home/user00/apps/tcm/bin"
        os.chdir(workdir)

    host_parser = HostParser("../cfg/host.xml")
    proc_parser = ProcParser("../cfg/proc.xml")
    proc_deploy_parser = ProcDeployParser("../cfg/proc_deploy.xml", args.worldid)
    bus_relation_parser = BusRelationParser("../cfg/bus_relation.xml")
    zone_group_name = ["pvpsvr", "gamesvr", "lockstepsvr", "lockstepmgrsvr"]

    ip_list = args.ips.strip().replace(" ", "").split(",")
    if args.type == "uncompress":
        uncompress_pkg(ip_list, args.args)
    elif args.type == "addcfg":  # 扩容添加tcm配置
        if ((not args.group_name or args.group_name.strip() == "") and
                (not args.zgroup_name or args.zgroup_name.strip() == "")):
            raise Exception("must give scale deploy_group name list or zgroup name")
        if (args.zgroup_name and not args.zoneid):
            raise Exception("must give zoneid when had zgroup_name")

        if args.group_name and args.zgroup_name:
            for i in args.group_name.split(","):
                if i in zone_group_name:
                    raise Exception("%s为zone模块下,不可填写" %i)
            if args.zgroup_name not in zone_group_name:
                raise Exception("%s不在zone模块下，不可填写" %(args.zgroup_name))
            group_name = args.group_name + "," + args.zgroup_name
            group_names = group_name.strip().replace(" ", "").split(",")
            print(group_names)
        elif args.group_name and not args.zgroup_name:
            for i in args.group_name.split(","):
                if i in zone_group_name:
                    raise Exception("%s为zone模块下,不可填写" %i)
            group_names = args.group_name.strip().replace(" ", "").split(",")
        else:
            if args.zgroup_name not in zone_group_name:
                raise Exception("%s不在zone模块下，不可填写" %(args.zgroup_name))
            group_names = args.zgroup_name.strip().replace(" ", "").split(",")
        if not args.env:
            raise Exception("must give env info")
        add_config(args.env, ip_list, group_names)
    elif args.type == "pushcfg":  # 推送配置到服务器
        pushcfg(ip_list)
    elif args.type == "start_tconnd":  # 启动tconnd
        start_tconnd(ip_list)
    elif args.type == "start":  # 启动除tconnd外的其它进程
        start(ip_list, DisableTconnd=True)
    elif args.type == "tickoff":  # gamesvr反注册服务，不会有新的请求进来
        lssvr_tickoff(ip_list)
    elif args.type == "stop":  # 缩容停服
        stop(ip_list)
    elif args.type == "delcfg":  # 删除缩容的tcm配置（host.xml proc_deploy.xml）
        del_config(ip_list)
    elif args.type == "clbinfo":  # 获取vport端口信息
        get_clb_info(ip_list)
    elif args.type == "getnames":  # 获取主机列表deploy_group 名称
        get_deploy_names(ip_list)